/**
 *
 * © Bahadir Zeren 2011
 * bzeren@gmail.com
 * No warranties, express or implied, are made for this program.
 *
 */

package org.baatar.opt.genetic.impl.prmopt;

import java.util.ArrayList;
import java.util.BitSet;
import org.baatar.opt.genetic.chromosome.IChromosome;
import org.baatar.opt.genetic.chromosome.MigrantChromosome;
import org.baatar.util.RandomUtil;

/**
 *
 * IChromosome implementation for parameter optimizer.
 * Instances of this class are composed of boolean gene values.
 * These boolean values are evaluated and used in the code like binary 1, 0 
 * values.
 * 
 */
public class Chromosome implements IChromosome<Boolean, BitSet> {

    private ChromosomeFactory chromosomeFactory = null;
    private int chromosomeLength = 0;

    private BitSet genes = null;
    private double fitness = 0.0;
    private ArrayList<Double> parameters = null;
    private boolean changed = true;

    public Chromosome(ChromosomeFactory chromosomeFactory) {
        this.chromosomeFactory = chromosomeFactory;
        this.chromosomeLength = chromosomeFactory.getChromosomeLength();
        genes = new BitSet(chromosomeLength);
        parameters = new ArrayList<Double>();
        for (int i = 0; i < chromosomeFactory.getParamInfos().size(); i++) {
            parameters.add(0.0);
        }
    }

    @Override
    public void initializeChromosome(RandomUtil random) {
    /**
     * 
     *  Initialize
     * 
     */
        for (int i = 0; i < chromosomeLength; i++)
            if (random.getRandDouble() > 0.5)
                setGeneValue(i, true);
        calculateFitness();
    }

    @Override
    public void initializeChromosome(BitSet migrantGenes) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

/*
 * Min + ((Max-Min) / (2^bit - 1)) * intdeger
 *
 * 0 + ((2.5 - 0) / (2^24 - 1)) intdeger
 *
 */
    @Override
    public void calculateFitness() {
        if (changed){
            ArrayList<ParamInfo> paramInfos = chromosomeFactory.getParamInfos();

            int geneIndex = 0;
            for (int i = 0; i < paramInfos.size(); i++) {

                ParamInfo paramInfo = paramInfos.get(i);

                BitSet subSet = genes.get(geneIndex, geneIndex + paramInfo.getBitSize());

                long bitInteger = 0;

                for(int j = 0 ; j < paramInfo.getBitSize(); j++)
                    if(subSet.get(j))
                        bitInteger |= ((long) 1 << j);

                double paramValue = paramInfo.getMinValue() +
                                    ((paramInfo.getMaxValue() -
                                        paramInfo.getMinValue()) /
                                        (Math.pow(2, paramInfo.getBitSize()) - 1)) *
                                        bitInteger;
                parameters.set(i, paramValue);

                geneIndex += paramInfo.getBitSize();
            }

            fitness = chromosomeFactory.runFitnessScript(parameters);

            changed = false;
        }
    }

    @Override
    public boolean isEqual(IChromosome<Boolean, BitSet> cand) {
        Chromosome candidate = (Chromosome) cand;
        for (int i = 0; i < chromosomeLength; i++)
            if (!candidate.getGeneValue(i).equals(this.getGeneValue(i)))
                return false;
        return true;
    }

    @Override
    public double getFitness() {
        return fitness;
    }

    @Override
    public Object getSolution() {
        return parameters;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        Chromosome chromosome = (Chromosome) super.clone();
        chromosome.genes = (BitSet) this.genes.clone();
        chromosome.parameters = (ArrayList<Double>) this.parameters.clone();
        return chromosome;
    }

    @Override
    public String toString() {
        if (parameters == null)
            return null;

        StringBuilder sb = new StringBuilder();

        sb.append("Fithness = ").
                append(Math.round(fitness * 10000.0) / 10000.0).
                append("; ");
        for (int i = 0; i < parameters.size(); i++)
            sb.append(chromosomeFactory.getParamInfos().get(i).getParamName()).
                    append(" = ").
                    append(Math.round(parameters.get(i) * 10000.0) / 10000.0).
                    append("; ")
                    ;
        return sb.toString();
    }

    @Override
    public Boolean getGeneValue(int index) {
        return genes.get(index);
    }

    @Override
    public void setGeneValue(int index, Boolean value) {
        if (value != genes.get(index)) {
            genes.set(index, value);
            changed = true;
        }
    }

    /**
     * @return the chromosomeLength
     */
    @Override
    public int getChromosomeLength() {
        return chromosomeLength;
    }

    @Override
    public MigrantChromosome<BitSet> extractMigrant(int clusterIndex, int optimizerIndex) {
        return new MigrantChromosome<BitSet>(clusterIndex, optimizerIndex, fitness, genes);
    }

    @Override
    public BitSet getGenes() {
        return genes;
    }

    @Override
    public boolean isMigrant() {
        return false;
    }

    @Override
    public void revertFitness() {
    }
}
